package org.javahispano.javacup.tacticas_aceptadas.willy;

import java.awt.Color;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;

import org.javahispano.javacup.modelo.Comando;
import org.javahispano.javacup.modelo.ComandoGolpearBalon;
import org.javahispano.javacup.modelo.ComandoIrA;
import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.EstiloUniforme;
import org.javahispano.javacup.modelo.JugadorDetalle;
import org.javahispano.javacup.modelo.Posicion;
import org.javahispano.javacup.modelo.SituacionPartido;
import org.javahispano.javacup.modelo.Tactica;
import org.javahispano.javacup.modelo.TacticaDetalle;

public class Periquitazo implements Tactica {

	/** Normal */
	Posicion alineacion1[] = new Posicion[]{
			new Posicion(0.0,-51.0), //1
			new Posicion(-7.16030534351145,-35.082089552238806), //2
			new Posicion(7.16030534351145,-35.6044776119403), //3
			new Posicion(21.251908396946565,-31.94776119402985), //4
			new Posicion(-21.84732824427481,-30.902985074626866), //5
			new Posicion(15.6993006993007,-5.463800904977376), //6
			new Posicion(-15.6993006993007,-5.9389140271493215), //7
			new Posicion(0.7132867132867133,-22.330316742081447), //8
			new Posicion(-13.79020979020979,32.794117647058822), //9
			new Posicion(-0.4755244755244755,12.076923076923077), //10
			new Posicion(5.027972027972028,41.794117647058822) //11
	};
	/** Perdiendo */
	Posicion alineacion2[] = new Posicion[]{
			new Posicion(0.0,-51.0),
			new Posicion(-11.888111888111888,-34.44570135746606),
			new Posicion(0.23776223776223776,-37.05882352941177),
			new Posicion(12.839160839160838,-35.15837104072398),
			new Posicion(8.321678321678322,-21.61764705882353),
			new Posicion(-7.132867132867133,-21.380090497737555),
			new Posicion(-0.23776223776223776,-3.5633484162895925),
			new Posicion(11.65034965034965,11.165158371040723),
			new Posicion(5.902097902097902,26.368778280542987),
			new Posicion(-11.65034965034965,11.165158371040723),
			new Posicion(-5.468531468531468,43.47285067873303)
	};
	Posicion alineacion3[] = new Posicion[]{
			new Posicion(0.2595419847328244, -50.41044776119403),
			new Posicion(-11.16030534351145, -35.78358208955224),
			new Posicion(12.717557251908397, -35.26119402985075),
			new Posicion(28.290076335877863, -28.470149253731343),
			new Posicion(-28.290076335877863, -28.470149253731343),
			new Posicion(14.793893129770993, -18.544776119402986),
			new Posicion(-17.389312977099234, -19.58955223880597),
			new Posicion(5.969465648854961, -5.485074626865671),
			new Posicion(-23.618320610687025, -0.7835820895522387),
			new Posicion(0.2595419847328244, -0.26119402985074625),
			new Posicion(22.580152671755727, -1.3059701492537314)
	};
	Posicion alineacion4[] = new Posicion[]{
			new Posicion(0.2595419847328244, -50.41044776119403),
			new Posicion(-11.16030534351145, -35.78358208955224),
			new Posicion(12.717557251908397, -35.26119402985075),
			new Posicion(28.290076335877863, -28.470149253731343),
			new Posicion(-28.290076335877863, -28.470149253731343),
			new Posicion(14.793893129770993, -18.544776119402986),
			new Posicion(-17.389312977099234, -19.58955223880597),
			new Posicion(6.4885496183206115, -6.529850746268657),
			new Posicion(-23.618320610687025, -0.7835820895522387),
			new Posicion(-6.4885496183206115, -6.529850746268657),
			new Posicion(23.618320610687025, -0.7835820895522387)
	};

	class TacticaDetalleImpl implements TacticaDetalle {

		public String getNombre() {
			return "Periquitazo";
		}

		public String getPais() {
			return "España";
		}

		public String getEntrenador() {
			return "willy";
		}

		public Color getColorCamiseta() {
			return new Color(0, 51, 204);
		}

		public Color getColorPantalon() {
			return new Color(0, 51, 204);
		}

		public Color getColorFranja() {
			return new Color(255, 255, 255);
		}

		public Color getColorCalcetas() {
			return new Color(255, 255, 255);
		}

		public Color getColorPortero() {
			return new Color(160, 255, 255);
		}

		public EstiloUniforme getEstilo() {
			return EstiloUniforme.FRANJA_VERTICAL;
		}

		public Color getColorCamiseta2() {
			return new Color(255, 0, 0);
		}

		public Color getColorPantalon2() {
			return new Color(255, 255, 255);
		}

		public Color getColorFranja2() {
			return new Color(0, 0, 0);
		}

		public Color getColorCalcetas2() {
			return new Color(255, 0, 0);
		}

		public Color getColorPortero2() {
			return new Color(3, 43, 218);
		}

		public EstiloUniforme getEstilo2() {
			return EstiloUniforme.LINEAS_VERTICALES;
		}

		class JugadorImpl implements JugadorDetalle {

			String nombre;
			int numero;
			Color piel, pelo;
			double velocidad, remate, presicion;
			boolean portero;
			Posicion posicion;

			public JugadorImpl(String nombre, int numero, Color piel, Color pelo,
					double velocidad, double remate, double presicion, boolean portero) {
				this.nombre=nombre;
				this.numero=numero;
				this.piel=piel;
				this.pelo=pelo;
				this.velocidad=velocidad;
				this.remate=remate;
				this.presicion=presicion;
				this.portero=portero;
			}

			public String getNombre() {
				return nombre;
			}

			public Color getColorPiel() {
				return piel;
			}

			public Color getColorPelo() {
				return pelo;
			}

			public int getNumero() {
				return numero;
			}

			public boolean esPortero() {
				return portero;
			}

			public double getVelocidad() {
				return velocidad;
			}

			public double getRemate() {
				return remate;
			}

			public double getPresicion() {
				return presicion;
			}

		}

		public JugadorDetalle[] getJugadores() {
			return new JugadorDetalle[]{
					new JugadorImpl("Jugador", 1, new Color(101, 72, 46), new Color(0,0,0),1.0d,1.0d,0.0d, true),
					new JugadorImpl("Jugador", 2, new Color(255,200,150), new Color(50,0,0),1.0d,1.0d,0.0d, false),
					new JugadorImpl("Jugador", 3, new Color(255,200,150), new Color(50,0,0),1.0d,1.0d,0.0d, false),
					new JugadorImpl("Jugador", 4, new Color(255,200,150), new Color(50,0,0),1.0d,0.75d,0.0d, false),
					new JugadorImpl("Jugador", 5, new Color(255,200,150), new Color(50,0,0),1.0d,0.75d,0.0d, false),
					new JugadorImpl("Jugador", 6, new Color(255,200,150), new Color(50,0,0),1.0d,1.0d,1.0d, false),
					new JugadorImpl("Jugador", 7, new Color(255,200,150), new Color(50,0,0),1.0d,1.0d,1.0d, false),
					new JugadorImpl("Jugador", 8, new Color(255,200,150), new Color(50,0,0),1.0d,1.0d,1.0d, false),
					new JugadorImpl("Jugador", 9, new Color(255,200,150), new Color(50,0,0),1.0d,1.0d,1.0d, false),
					new JugadorImpl("Jugador", 10, new Color(255,200,150), new Color(50,0,0),1.0d,1.0d,1.0d, false),
					new JugadorImpl("Jugador", 11, new Color(255,200,150), new Color(50,0,0),1.0d,1.0d,1.0d, false)
			};
		}
	}

	TacticaDetalle detalle=new TacticaDetalleImpl();
	public TacticaDetalle getDetalle() {
		return detalle;
	}

	public Posicion[] getPosicionSaca(SituacionPartido sp) {
		return alineacion3;
	}

	public Posicion[] getPosicionRecive(SituacionPartido sp) {
		return alineacion4;
	}

	private Posicion[] getAlineacion() {
		// Empatando +-1
		if (golesPropios + 1 >= golesRival &&
				golesPropios - 1 <= golesRival) {
			return alineacion1;
		} 
		// Perdiendo 1+
		else if (golesPropios + 1 < golesRival) {
			return alineacion2;
		} 
		// Gandando 1+
		else {
			return alineacion1;
		}
	}

	ArrayList<Comando> comandos = new ArrayList<Comando>();
	Random r = new Random();
	boolean antSaco = false;
	boolean saco = false;
	int itersaco = 0;
	ArrayList<Desmarque> desmarques = new ArrayList<Desmarque>();
	int indPorteroRival;
	int indPortero;
	boolean estoyAtacando = false;
	Posicion balonAnterior;
	double alturaBalonAnterior;
	int golesPropios, golesRival;
	Posicion[] posRivalesAnterior;
	JugadorDetalle[] detalleRivales;
	int iteracion;

	public List<Comando> ejecutar(SituacionPartido sp) {
		iteracion = sp.iteracion();
		if (detalleRivales == null) {
			detalleRivales = sp.detalleJugadoresRivales();
		}
		golesPropios = sp.golesPropios();
		golesRival = sp.golesRival();
		saco = sp.saco() || itersaco > 0;
		for(int i=0; i<detalle.getJugadores().length;  i++) {
			if (detalle.getJugadores()[i].esPortero()) {
				indPortero = i;
				break;
			}
		}
		for (int i=0; i<sp.detalleJugadoresRivales().length; i++) {
			if (sp.detalleJugadoresRivales()[i].esPortero()) {
				indPorteroRival = i;
				break;
			}
		}
		comandos.clear();
		if (!antSaco && sp.saco()) {
			itersaco = 20;
		}
		if (itersaco > 0) {
			itersaco--;
		}

		Posicion[] pos = sp.misJugadores();

		comprobarAtacando(sp.balon(), pos, sp.rivales(), sp.sacaRival(), sp.alturaBalon());

		movimientosPrevios(pos, sp.rivales(), sp.balon(), sp.alturaBalon());

		if (itersaco == 0) {
			for (int i : sp.puedenRematar()) {
				if (sp.iteracionesParaRematar()[i] > 0) {
					continue;
				}
				boolean hayPase = false;
				boolean puedeCorrer = puedeCorrer(pos, sp.rivales(), i);
				boolean dentroArea = dentroArea(pos[i]);
				boolean hayRegate = false;
				if ((!puedeCorrer || dentroArea) && 
						i != indPortero) {
					for (int j : pos[i].indicesMasCercanos(pos, i)) {
						boolean estaMejorSituado = estaMejorSituado(pos[i], pos[j], sp.rivales());
						if (((dentroArea && estaMejorSituado) || 
								(!dentroArea && pos[j].getY() > pos[i].getY())) && 
								pos[i].distancia(pos[j]) > 6) {
							hayPase = darPase(pos, i, j, true, sp.rivales());
							if (hayPase) {
								break;
							}
						}
					}
				}
				if (!hayPase) {
					if (i != indPortero) {
						hayRegate = regate(pos, sp.rivales(), i);
					}
					if (!hayRegate && 
							!patapumParriba(i, pos, sp.rivales())) {
						forzarPase(pos, i, pos[i].indiceMasCercano(pos, i), true, sp.rivales());
					}
				}

				boolean baselina = baselina(i, pos, sp.rivales());
				boolean porteriaVacia = porteroMalSituado(pos[i], sp.rivales());
				if (porteriaVacia ||
						(!hayRegate || 
								pos[i].distancia(Constantes.centroArcoSup) < Constantes.ITERACIONES_GOLPEAR_BALON * Constantes.VELOCIDAD_MAX * 1.5 + Constantes.DISTANCIA_CONTROL_BALON_PORTERO) && 
								!hayPase && 
								!baselina) {
					chutarAPuerta(i, pos, sp.rivales());
				}
			}
			jugadasEnsayadas(pos, sp.rivales(), sp.balon(), sp.saco());
		}
		balonAnterior = sp.balon();
		posRivalesAnterior = sp.rivales().clone();
		alturaBalonAnterior = sp.alturaBalon();
		antSaco = sp.saco();
		actualizarDesmarques();
		return comandos;
	}

	private Posicion calculaObjetivoJugador(int indJugador, Posicion[] pos) {
		if (Math.abs(90 - getAnguloDisparo(pos[indJugador])) > 60 ||
				dentroArea(pos[indJugador])) {
			return pos[indJugador].getX() > 0 ? Constantes.posteIzqArcoSup : Constantes.posteDerArcoSup;
		} else {
			return new Posicion(pos[indJugador].getX(), Constantes.LARGO_CAMPO_JUEGO/2);
		}
	}

	private boolean porteroMalSituado(Posicion jugador, Posicion[] posRivales) {
		double anguloIzq = Math.toDegrees(jugador.angulo(Constantes.posteIzqArcoSup));
		double anguloDer = Math.toDegrees(jugador.angulo(Constantes.posteDerArcoSup));
		double anguloPortero = Math.toDegrees(jugador.angulo(posRivales[indPorteroRival]));

		return (Math.abs(anguloDer - anguloPortero) > 2 * Math.abs(anguloIzq - anguloPortero) ||
				Math.abs(anguloIzq - anguloPortero) > 2 * Math.abs(anguloDer - anguloPortero) ||
				Math.abs(anguloIzq - anguloDer) < Math.abs(anguloDer - anguloPortero) ||
				Math.abs(anguloIzq - anguloDer) < Math.abs(anguloIzq - anguloPortero)) &&
				(Math.abs(90 - getAnguloDisparo(jugador)) < 60 ||
						jugador.distancia(Constantes.centroArcoSup) < 1.5 * Constantes.LARGO_ARCO/2);
	}

	private int indicePrimeroEnLlegarAlBalon(Posicion[] pos, Posicion balon, double alturaBalon) {
		int jugador = balon.indiceMasCercano(pos);
		if (balonAnterior == null) {
			return jugador;
		}

		// Velocidad en m/iter
		double velocidadBalon = balon.distancia(balonAnterior);
		double balonDz = alturaBalon - alturaBalonAnterior;

		if (velocidadBalon == 0) {
			return jugador;
		}

		int iter = 0;
		boolean encontrado = false;
		Posicion pBalon = new Posicion(balon);

		while (!encontrado && iter < 50) {
			for (int i=0; i<pos.length; i++) {
				double distancia = pos[i].distancia(pBalon);
				if (distancia - iter * Constantes.VELOCIDAD_MAX < Constantes.DISTANCIA_CONTROL_BALON &&
						alturaBalon <= Constantes.ALTURA_CONTROL_BALON) {
					encontrado = true;
					jugador = i;
					break;
				}
			}
			double factorReduccion;
			if (alturaBalon < Constantes.G * 3) {
				factorReduccion = Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;	
			} else {
				factorReduccion = Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
			}
			velocidadBalon *= factorReduccion;
			balonDz = Math.round((balonDz - Constantes.G) / Constantes.G) * Constantes.G;
			alturaBalon += balonDz;
			if (alturaBalon < 0) {
				alturaBalon = 0;
			}
			pBalon = pBalon.moverAngulo(balonAnterior.angulo(balon), velocidadBalon);
			if (!estaDentroCampo(pBalon)) {
				return -1;
			}
			iter++;
		}

		return jugador;
	}

	private boolean estaDentroCampo(Posicion pos) {
		if (pos.getX() < -Constantes.ANCHO_CAMPO_JUEGO/2 ||
				pos.getX() > Constantes.ANCHO_CAMPO_JUEGO/2 ||
				pos.getY() < -Constantes.LARGO_CAMPO_JUEGO/2 ||
				pos.getY() > Constantes.LARGO_CAMPO_JUEGO/2) {
			return false;
		}
		return true;
	}

	private boolean llegaAntesAlBalon(Posicion jugador, Posicion rival, boolean jugadorEsPortero, boolean rivalEsPortero, Posicion balon, double alturaBalon) {
		if (balonAnterior == null) {
			return false;
		}

		// Velocidad en m/iter
		double velocidadBalon = balon.distancia(balonAnterior);
		double balonDz = alturaBalon - alturaBalonAnterior;

		boolean encontradoJugador = false;
		boolean encontradoRival = false;
		int iter = 0;

		Posicion pBalon = new Posicion(balon);
		while (!encontradoJugador && !encontradoRival && iter < 50) {
			double distancia = jugador.distancia(pBalon);
			double distanciaRival = rival.distancia(pBalon);
			if (distancia - iter * Constantes.VELOCIDAD_MAX < (jugadorEsPortero?Constantes.DISTANCIA_CONTROL_BALON_PORTERO:Constantes.DISTANCIA_CONTROL_BALON) &&
					alturaBalon < (jugadorEsPortero?Constantes.ALTO_ARCO:Constantes.ALTURA_CONTROL_BALON)) {
				encontradoJugador = true;
			}
			if (distanciaRival - iter * Constantes.VELOCIDAD_MAX < (rivalEsPortero?Constantes.DISTANCIA_CONTROL_BALON_PORTERO:Constantes.DISTANCIA_CONTROL_BALON) &&
					alturaBalon < (rivalEsPortero?Constantes.ALTO_ARCO:Constantes.ALTURA_CONTROL_BALON)) {
				encontradoRival = true;
			}
			double factorReduccion;
			if (alturaBalon < Constantes.G * 3) {
				factorReduccion = Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;	
			} else {
				factorReduccion = Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
			}
			velocidadBalon *= factorReduccion;
			balonDz = Math.round((balonDz - Constantes.G) / Constantes.G) * Constantes.G;
			alturaBalon += balonDz;
			pBalon = pBalon.moverAngulo(balonAnterior.angulo(balon), velocidadBalon);
			iter++;
		}

		if (encontradoJugador) {
			return true;
		}

		return false;
	}

	private Posicion interceptaBalon(Posicion jugador, Posicion balon, double alturaBalon) {
		if (balonAnterior == null) {
			return balon;
		}

		// Velocidad en m/iter
		double velocidadBalon = balon.distancia(balonAnterior);
		double balonDz = alturaBalon - alturaBalonAnterior;

		boolean encontrado = false;
		int iter = 0;

		Posicion pBalon = new Posicion(balon);
		while (!encontrado && iter < 50) {
			double distancia = jugador.distancia(pBalon);
			if (distancia - iter * Constantes.VELOCIDAD_MAX < Constantes.DISTANCIA_CONTROL_BALON &&
					alturaBalon <= Constantes.ALTURA_CONTROL_BALON) {
				encontrado = true;
			}
			double factorReduccion;
			if (alturaBalon < Constantes.G * 3) {
				factorReduccion = Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;	
			} else {
				factorReduccion = Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
			}
			velocidadBalon *= factorReduccion;
			balonDz = Math.round((balonDz - Constantes.G) / Constantes.G) * Constantes.G;
			alturaBalon += balonDz;
			pBalon = pBalon.moverAngulo(balonAnterior.angulo(balon), velocidadBalon);
			iter++;
		}

		return pBalon.setDentroCampoJuego();
	}

	private Posicion calculaDestinoRemate(Posicion jugador, Posicion porteroRival) {
		Posicion p = null;
		double anguloIzq = Math.toDegrees(jugador.angulo(Constantes.posteIzqArcoSup));
		double anguloDer = Math.toDegrees(jugador.angulo(Constantes.posteDerArcoSup));
		double anguloPortero = Math.toDegrees(jugador.angulo(porteroRival));

		if (Math.abs(anguloIzq - anguloDer) < Math.abs(anguloDer - anguloPortero) ||
				Math.abs(anguloIzq - anguloDer) < Math.abs(anguloIzq - anguloPortero)) {
			// Si la porteria esta vacia, chutamos al centro
			p = Constantes.centroArcoSup;
		} else if (Math.abs(anguloIzq - anguloPortero) + getRandomDeviation(3) < Math.abs(anguloDer - anguloPortero)) {
			// Chutaremos por la derecha
			p = Posicion.media(Posicion.media(porteroRival, Constantes.posteDerArcoSup), Constantes.posteDerArcoSup);
		} else{
			// Chutaremos por la izquierda
			p = Posicion.media(Posicion.media(porteroRival, Constantes.posteIzqArcoSup), Constantes.posteIzqArcoSup);
		}

		return p;
	}

	private boolean estaMejorSituado(Posicion pos1, Posicion pos2, Posicion[] posRivales) {
		double distanciaRivalMasCercano = pos1.distancia(posRivales[pos1.indiceMasCercano(posRivales)]); 
		double distanciaRivalMasCercano2 = pos2.distancia(posRivales[pos2.indiceMasCercano(posRivales)]);

		if (!porteroMalSituado(pos1, posRivales) &&
				porteroMalSituado(pos1, posRivales)) {
			return true;
		}

		//		if (pos1.distancia(posRivales[indPorteroRival]) < Constantes.DISTANCIA_CONTROL_BALON_PORTERO * 3) {
		//			return true;
		//		}

		if (distanciaRivalMasCercano > distanciaRivalMasCercano2 + 6) {
			return false;
		}

		if (!dentroArea(pos2)) {
			return false;
		}

		double angulo1 = getAnguloDisparo(pos1);
		double angulo2 = getAnguloDisparo(pos2);

		if (Math.abs(90-angulo1) < Math.abs(90-angulo2)) {
			return false;
		}

		return true;
	}

	private void iniciarDesmarque(int indJugador, Posicion[] pos, Posicion[] posRivales, Posicion balon) {
		Posicion pAdelantada = getAlineacion()[indJugador].moverAngulo(pos[indJugador].angulo(calculaObjetivoJugador(indJugador, pos)), 5 + getRandomDeviation(5)).setDentroCampoJuego();
		Posicion pRetrasada = getAlineacion()[indJugador].moverAngulo(pos[indJugador].angulo(balon), 5 + getRandomDeviation(5)).setDentroCampoJuego();

		double distanciaActual = pos[indJugador].distancia(posRivales[pos[indJugador].indiceMasCercano(posRivales)]);
		double distanciaPAdelantada = pAdelantada.distancia(posRivales[pAdelantada.indiceMasCercano(posRivales)]);
		double distanciaPRetrasada = pRetrasada.distancia(posRivales[pRetrasada.indiceMasCercano(posRivales)]);

		Desmarque d = null;

		if (distanciaPAdelantada > distanciaPRetrasada || distanciaPAdelantada > distanciaActual) {
			d = new Desmarque(indJugador, pAdelantada);
		} else if (distanciaActual < distanciaPRetrasada) {
			d = new Desmarque(indJugador, pRetrasada);
		}
		if (d != null && !desmarques.contains(d)) {
			desmarques.add(d);
		}
	}

	private void cubrirRivales(Posicion[] pos, Posicion[] posRivales, Posicion balon) {
		if (estoyAtacando) {
			return;
		}
		List<Integer> estaCubriendo = new ArrayList<Integer>();
		for (int i=0; i<posRivales.length; i++) {
			if (posRivales[i].getY() < Constantes.LARGO_CAMPO_JUEGO/4) {
				Posicion p = posRivales[i].moverAngulo(posRivales[i].angulo(balon), 2);
				double distanciaJugadorMasCercano = Constantes.LARGO_CAMPO_JUEGO;
				int indJugadorMasCercano = -1;
				for (int j=0; j<pos.length; j++) {
					if (j != indPortero &&
							!estaCubriendo.contains(j) &&
							posRivales[i].distancia(pos[j]) < distanciaJugadorMasCercano) {

						indJugadorMasCercano = j;
						distanciaJugadorMasCercano = posRivales[i].distancia(pos[j]);
					}
				}
				if (indJugadorMasCercano > -1 &&
						pos[indJugadorMasCercano].distancia(getAlineacion()[indJugadorMasCercano]) < 15) {
					addComando(new ComandoIrA(indJugadorMasCercano, p));
					estaCubriendo.add(indJugadorMasCercano);
				}
			}
		}
	}

	private void movimientosPrevios(Posicion[] pos, Posicion[] posRivales, Posicion balon, double alturaBalon) {
		for (int i = 0; i < 11; i++) {
			if (estoyAtacando && i != indPortero) {
				if (pos[i].getY() + 5 > balon.getY() || 
						pos[i].getY() > Constantes.LARGO_CAMPO_JUEGO / 4) {
					iniciarDesmarque(i, pos, posRivales, balon);
				} else {
					addComando(new ComandoIrA(i, getAlineacion()[i]), false);
				}
			} else {
				addComando(new ComandoIrA(i, getAlineacion()[i]), false);
			}
		}

		Desmarque desmarque = null;
		for(ListIterator<Desmarque> it = desmarques.listIterator(); it.hasNext(); ) {
			desmarque = it.next();
			if (!desmarque.haLlegado(pos[desmarque.getIndiceJugador()], balon)) {
				addComando(desmarque.getComandoIrA(pos[desmarque.getIndiceJugador()], balon, alturaBalon), false);
			} else {
				desmarque.setEliminar(true);
			}
		}

		moverPortero(pos, posRivales, balon, alturaBalon);

		cubrirRivales(pos, posRivales, balon);

		int indMasCercano1, indMasCercano2;
		int indPrimeroEnLlegar = indicePrimeroEnLlegarAlBalon(pos, balon, alturaBalon);
		if (indPortero == balon.indiceMasCercano(getAlineacion()) && saco) {
			indMasCercano1 = indPortero;
			indMasCercano2 = -1;
		} else if (saco) {
			indMasCercano1 = balon.indiceMasCercano(getAlineacion(), indPortero);
			indMasCercano2 = -1;
		} else {
			if (estoyAtacando) {
				indMasCercano1 = balon.indicesMasCercanos(pos, indPortero)[0];
				indMasCercano2 = -1;
			} else {
				indMasCercano1 = balon.indicesMasCercanos(getAlineacion(), indPortero)[0];
				indMasCercano2 = balon.indicesMasCercanos(getAlineacion(), indPortero)[1];
			}
		}
		addComando(new ComandoIrA(indMasCercano1, interceptaBalon(pos[indMasCercano1], balon, alturaBalon)));
		if (indMasCercano2 > -1) {
			addComando(new ComandoIrA(indMasCercano2, interceptaBalon(pos[indMasCercano2], balon, alturaBalon)));
		}
		if (indPrimeroEnLlegar > -1 &&
				indPrimeroEnLlegar != indPortero &&
				indPrimeroEnLlegar != indMasCercano1 &&
				indPrimeroEnLlegar != indMasCercano2) {
			addComando(new ComandoIrA(indPrimeroEnLlegar, interceptaBalon(pos[indPrimeroEnLlegar], balon, alturaBalon)));
		}

	}

	private boolean dentroArea(Posicion posJugador) {
		return dentroArea(posJugador, true);
	}

	private boolean dentroArea(Posicion posJugador, boolean areaSuperior) {
		if (Math.abs(posJugador.getX()) > Constantes.LARGO_AREA_GRANDE / 2) {
			return false;
		}
		if (areaSuperior) {
			if (posJugador.getY() < Constantes.LARGO_CAMPO_JUEGO / 2 - Constantes.ANCHO_AREA_GRANDE) {
				return false;
			}
		} else {
			if (posJugador.getY() > -Constantes.LARGO_CAMPO_JUEGO / 2 + Constantes.ANCHO_AREA_GRANDE) {
				return false;
			}
		}
		return true;
	}

	private boolean jugadasEnsayadas(Posicion[] pos, Posicion[] posRivales, Posicion balon, boolean saco) {
		if (!saco) {
			return false;
		}
		int indJugador = balon.indiceMasCercano(pos);
		if (pos[indJugador].distancia(balon) > Constantes.DISTANCIA_CONTROL_BALON) {
			return false;
		}

		// Corner
		if (Constantes.cornerSupDer.distancia(balon) < 1 ||
				Constantes.cornerSupIzq.distancia(balon) < 1) {
			if (darPase(pos, indJugador, pos[indJugador].indiceMasCercano(pos, indJugador), true, posRivales)) {
				return true;
			} else {
				addComando(new ComandoIrA(indJugador, Constantes.penalSup));
				addComando(new ComandoGolpearBalon(indJugador));
				return true;
			}
		}

		return false;
	}

	private double getAnguloDisparo(Posicion posJugador) {
		return Math.toDegrees(posJugador.angulo(Constantes.centroArcoSup));
	}

	/*
	private boolean comprobarPocoAngulo(Posicion[] pos, Posicion[] posRivales, int indJugador) {
		if (Math.abs(90 - getAnguloDisparo(pos[indJugador])) > 60) {
			return ganarAngulo(pos, indJugador);
		}
		return false;
	}

	private boolean ganarAngulo(Posicion[] pos, int indJugador) {
		double anguloObjetivo = pos[indJugador].angulo(Constantes.centroArcoSup);

		if (Math.toDegrees(anguloObjetivo) < 90) {
			anguloObjetivo -= Math.toRadians(60);
		} else {
			anguloObjetivo += Math.toRadians(60);
		}

		Posicion p = pos[indJugador].moverAngulo(anguloObjetivo, Constantes.VELOCIDAD_MAX);
		addComando(new ComandoIrA(indJugador, p.setDentroCampoJuego()));
		addComando(new ComandoGolpearBalon(indJugador));
		return true;
	}*/

	private void comprobarAtacando(Posicion balon, Posicion[] pos, Posicion[] posRivales, boolean sacaRival, double alturaBalon) {
		if (sacaRival ||
				(balon.distancia(posRivales[balon.indiceMasCercano(posRivales)]) <= Constantes.DISTANCIA_CONTROL_BALON &&
						alturaBalon <= Constantes.ALTURA_CONTROL_BALON)) {
			estoyAtacando = false;
		}
	}

	private boolean baselina(int indJugador, Posicion[] pos, Posicion[] posRivales) {
		double remate = detalle.getJugadores()[indJugador].getRemate();
		double distancia = pos[indJugador].distancia(Constantes.centroArcoSup);
		double distanciaRelativa = distancia/2 + distancia/(2*remate);
		double minDistBaselina = 12;
		double maxDistBaselina = 30;

		// Baselina
		if (distanciaRelativa > minDistBaselina && 
				distanciaRelativa < maxDistBaselina &&
				posRivales[indPorteroRival].distancia(Constantes.centroArcoSup) > Constantes.ANCHO_AREA_CHICA &&
				posRivales[indPorteroRival].distancia(pos[indJugador]) > Constantes.DISTANCIA_CONTROL_BALON_PORTERO * 3 &&
				Math.abs(90 - getAnguloDisparo(pos[indJugador])) < 60) {
			// dist=12,  rem=0.7
			// dist=30, rem=0.95
			double fuerzaBaselina = 0.7 + 0.25 * (distanciaRelativa - minDistBaselina) / (maxDistBaselina - minDistBaselina);
			addComando(new ComandoGolpearBalon(indJugador, Constantes.centroArcoSup, fuerzaBaselina, 40));
			return true;
		}

		return false;
	}

	private boolean chutarAPuerta(int indJugador, Posicion[] pos, Posicion[] posRivales) {
		double remate = detalle.getJugadores()[indJugador].getRemate();
		double distancia = pos[indJugador].distancia(Constantes.centroArcoSup);
		double distanciaRelativa = distancia/2 + distancia/(2*remate);

		Posicion destinoRemate = calculaDestinoRemate(pos[indJugador], posRivales[indPorteroRival]);

		if (distanciaRelativa < 10) {
			double angulo = 20;
			addComando(new ComandoGolpearBalon(indJugador, destinoRemate, 1, angulo));
			return true;
		} else if (distanciaRelativa < 20) {
			double angulo = 20;
			addComando(new ComandoGolpearBalon(indJugador, destinoRemate, 1, angulo));
			return true;
		} else if (distanciaRelativa < 27) {
			double angulo = 20 + 10*(distanciaRelativa-20) / (27 - 20);
			addComando(new ComandoGolpearBalon(indJugador, destinoRemate, 1, angulo));
			return true;
		}

		return false;
	}

	private boolean parada(Posicion[] pos, Posicion[] posRivales, Posicion balon, double alturaBalon) {
		if (balonAnterior == null) {
			return false;
		}
		if (Constantes.centroArcoInf.distancia(balon) > 35) {
			return false;
		}

		if (saco) {
			return false;
		}

		if (estoyAtacando) {
			return false;
		}

		// Salir a por el balon
		int indRivalMasCercano = indicePrimeroEnLlegarAlBalon(posRivales, balon, alturaBalon);
		if (indRivalMasCercano > -1 &&
				(indPortero == balon.indiceMasCercano(pos) ||
						indPortero == indicePrimeroEnLlegarAlBalon(pos, balon, alturaBalon)) &&
						llegaAntesAlBalon(pos[indPortero], posRivales[indRivalMasCercano], true, false, balon, alturaBalon)) {
			addComando(new ComandoIrA(indPortero, interceptaBalon(pos[indPortero], balon, alturaBalon)));
			return true;
		}

		// Va hacia atras
		if (Constantes.centroArcoInf.distancia(balon) > Constantes.centroArcoInf.distancia(balonAnterior)) {
			return false;
		}
		// Velocidad en m/iter
		double velocidadBalon = balon.distancia(balonAnterior);
		// No es un chut
		if (velocidadBalon < 1.3) {
			return false;
		}
		Posicion destinoBalon = Geometry.lineIntersection(balon, balonAnterior, Constantes.posteDerArcoInf, Constantes.posteIzqArcoInf);
		if (destinoBalon == null) {
			return false;
		}
		// Va fuera
		if (Math.abs(destinoBalon.getX()) > Constantes.LARGO_ARCO / 2 + Constantes.RADIO_BALON * 2) {
			return false;
		}

		addComando(new ComandoIrA(indPortero, destinoBalon.moverAngulo(destinoBalon.angulo(balon), Constantes.DISTANCIA_CONTROL_BALON_PORTERO).setDentroCampoJuego()));

		return true;
	}

	private void moverPortero(Posicion[] pos, Posicion[] posRivales, Posicion balon, double alturaBalon) {
		boolean hayParada = parada(pos, posRivales, balon, alturaBalon); 
		if (hayParada) {
			return;
		}
		Posicion newPos = Geometry.lineIntersection(balon, Constantes.centroArcoInf, getAlineacion()[indPortero], getAlineacion()[indPortero].moverPosicion(5, 0));
		if (newPos == null) {
			return;
		}
		if (Math.abs(newPos.getX()) > Constantes.LARGO_ARCO/2 + Constantes.RADIO_BALON*2) {
			newPos = new Posicion(Math.signum(newPos.getX())*(Constantes.LARGO_ARCO/2 + Constantes.RADIO_BALON*2), newPos.getY());
		}
		addComando(new ComandoIrA(indPortero, newPos.setDentroCampoJuego()));
		//		if (balon.distancia(newPos) < Constantes.ANCHO_AREA_CHICA) {
		//			if (alturaBalon <= Constantes.ALTO_ARCO) {
		//				if (rivalesEnArea(posRivales) < 2 || 
		//						(balon.distancia(posRivales[balon.indiceMasCercano(posRivales)]) > balon.distancia(pos[indPortero]) &&
		//								balon.getY() < balonAnterior.getY())) {
		//					addComando(new ComandoIrA(indPortero, balon.setDentroCampoJuego()));
		//				} else {
		//					Posicion p = new Posicion(Math.min(Math.abs(balon.getX()), Constantes.LARGO_ARCO/2)*Math.signum(balon.getX()), -Constantes.LARGO_CAMPO_JUEGO / 2);
		//					addComando(new ComandoIrA(indPortero, p.setDentroCampoJuego()));
		//				}
		//			} else {
		//				Posicion p = new Posicion(Math.min(Math.abs(balon.getX()), Constantes.LARGO_ARCO/2)*Math.signum(balon.getX()), -Constantes.LARGO_CAMPO_JUEGO / 2);
		//				addComando(new ComandoIrA(indPortero, p.setDentroCampoJuego()));
		//			}
		//		} else if (balon.distancia(newPos) < Constantes.DISTANCIA_PENAL) {
		//			if (alturaBalon <= Constantes.ALTO_ARCO && 
		//					(rivalesEnArea(posRivales) < 2 || 
		//							balon.distancia(posRivales[balon.indiceMasCercano(posRivales)]) > balon.distancia(pos[indPortero]))) {
		//				addComando(new ComandoIrA(indPortero, interceptaBalon(pos[indPortero], balon, alturaBalon)));
		//			}
		//		}
	}

	private boolean patapumParriba(int indJugador, Posicion[] pos, Posicion[] posRivales) {

		if (pos[indJugador].getY() > 0) {
			return false;
		}

		double anguloObjetivo = Math.toDegrees(pos[indJugador].angulo(Constantes.centroArcoSup));
		Posicion p = Constantes.centroArcoSup;

		if (posRivales != null) {
			Posicion rival = posRivales[pos[indJugador].indiceMasCercano(posRivales)];
			double distancia = rival.distancia(pos[indJugador]);

			if (distancia < 6 && pos[indJugador].getY() < rival.getY()) {
				double anguloRival = pos[indJugador].angulo(rival);
				if (anguloObjetivo > Math.toDegrees(anguloRival)) {
					anguloObjetivo += Math.min(30,30/(distancia));
				} else {
					anguloObjetivo -= Math.min(30,30/(distancia));
				}
				p = pos[indJugador].moverAngulo(Math.toRadians(anguloObjetivo), 6);
			}
		}
		addComando(new ComandoGolpearBalon(indJugador, p, 1, Constantes.ANGULO_VERTICAL));
		estoyAtacando = false;
		return true;
	}

	private boolean puedeCorrer(Posicion[] pos, Posicion[] posRivales, int indJugador) {
		if (indJugador == Constantes.centroArcoSup.indiceMasCercano(pos)) {
			return true;
		}
		if (pos[indJugador].distancia(getAlineacion()[indJugador]) > 15 
				&& !saco) {
			return false;
		}
		if (indJugador == indPortero) {
			return false;
		}
		int indRival = pos[indJugador].indiceMasCercano(posRivales);
		if (posRivales[indRival].distancia(pos[indJugador]) < 2 * 1.35 * Constantes.VELOCIDAD_MAX * Constantes.ITERACIONES_GOLPEAR_BALON + (indRival == indPorteroRival ? Constantes.DISTANCIA_CONTROL_BALON_PORTERO : Constantes.DISTANCIA_CONTROL_BALON)) {
			return false;
		}
		//		double anguloObjetivo = pos[indJugador].angulo(calculaObjetivoJugador(indJugador, pos));
		//		Posicion p = pos[indJugador].moverAngulo(anguloObjetivo, Constantes.VELOCIDAD_MAX * Constantes.ITERACIONES_GOLPEAR_BALON * 1.3);
		//
		//		if (p.distancia(posRivales[p.indiceMasCercano(posRivales)]) < 1.5 * Constantes.VELOCIDAD_MAX * Constantes.ITERACIONES_GOLPEAR_BALON) {
		//			return false;
		//		}

		return true;
	}

	private boolean regate(Posicion[] pos, Posicion[] posRivales, int indJugador) {
		int indRivalMasCercano = pos[indJugador].indiceMasCercano(posRivales);
		for (int i:pos[indJugador].indicesMasCercanos(posRivales)) {
			if (posRivales[i].distancia(calculaObjetivoJugador(indJugador, pos)) <
					pos[indJugador].distancia(calculaObjetivoJugador(indJugador, pos))) {
				indRivalMasCercano = i;
				break;
			}
		}
		Posicion rivalMasCercano = posRivales[indRivalMasCercano];
		double distanciaRivalMasCercano = rivalMasCercano.distancia(pos[indJugador]);

		if (distanciaRivalMasCercano <= Constantes.DISTANCIA_CONTROL_BALON || 
				(indRivalMasCercano == indPorteroRival && distanciaRivalMasCercano < Constantes.DISTANCIA_CONTROL_BALON_PORTERO)) {
			return abortarRegate(pos, posRivales, indJugador);
		}

		SituacionJugador sj = new SituacionJugador(pos[indJugador], calculaObjetivoJugador(indJugador, pos));
		if (posRivalesAnterior != null) {
			for (int i=0; i<posRivales.length; i++) {
				Posicion posRiv = posRivales[i];
				double distanciaRiv = pos[indJugador].distancia(posRiv);
				double velocidadRiv = Constantes.getVelocidad(detalleRivales[i].getVelocidad());
				/* factorAcercamiento:
				 *  -1: se aleja directamente
				 *   0: esta quieto (ni se acerca ni se aleja)
				 *   1: se acerca directamente
				 */
				double factorAcercamiento = (pos[indJugador].distancia(posRivalesAnterior[i]) - pos[indJugador].distancia(posRiv)) / velocidadRiv;
				double distanciaSeguridad = (velocidadRiv * Constantes.ITERACIONES_GOLPEAR_BALON * 1.35) + factorAcercamiento * (velocidadRiv * Constantes.ITERACIONES_GOLPEAR_BALON * 1.35);
				//double distanciaSeguridad = (velocidadRiv * Constantes.ITERACIONES_GOLPEAR_BALON * 1.35) * 2;
				if (i == indPorteroRival) {
					distanciaSeguridad += Constantes.DISTANCIA_CONTROL_BALON_PORTERO;
				} else {
					distanciaSeguridad += Constantes.DISTANCIA_CONTROL_BALON;
				}
				if (distanciaRiv < distanciaSeguridad &&
						pos[indJugador].distancia(calculaObjetivoJugador(indJugador, pos)) > posRiv.distancia(calculaObjetivoJugador(indJugador, pos))) {

					sj.addRival(posRiv);
				}
			}
		}
		Posicion p = sj.getSalida();
		// Si no hay salida o hemos de retroceder
		if (p == null 
				//|| p.distancia(calculaObjetivoJugador(indJugador, pos)) > pos[indJugador].distancia(calculaObjetivoJugador(indJugador, pos))
		) {
			return false;
			//return abortarRegate(pos, posRivales, indJugador);
		}
		double velRemate = Constantes.getVelocidadRemate(detalle.getJugadores()[indJugador].getRemate());
		addComando(new ComandoIrA(indJugador, p));
		addComando(new ComandoGolpearBalon(indJugador, p, 1d / (velRemate*1.15), 0));
		return true;
	}

	private Posicion meterDesplazamientoDentroCampo(Posicion jugador, Posicion destino) {
		if (destino == null) {
			return null;
		}
		double distancia = jugador.distancia(destino);
		double fondoSup = Constantes.LARGO_CAMPO_JUEGO/2 - Constantes.DISTANCIA_CONTROL_BALON;
		double fondoInf = -(Constantes.LARGO_CAMPO_JUEGO/2 - Constantes.DISTANCIA_CONTROL_BALON);
		double bandaDer = Constantes.ANCHO_CAMPO_JUEGO/2 - Constantes.DISTANCIA_CONTROL_BALON;
		double bandaIzq = -(Constantes.ANCHO_CAMPO_JUEGO/2 - Constantes.DISTANCIA_CONTROL_BALON);
		Posicion cornerInfDer = new Posicion(bandaDer, fondoInf);
		Posicion cornerSupDer = new Posicion(bandaDer, fondoSup);
		Posicion cornerInfIzq = new Posicion(bandaIzq, fondoInf);
		Posicion cornerSupIzq = new Posicion(bandaIzq, fondoSup);
		Posicion ret = new Posicion(destino);

		if (ret.getX() > bandaDer) {
			Posicion intersecciones[] = Geometry.lineCircleIntersection(cornerInfDer, cornerSupDer, jugador, distancia);
			if (intersecciones != null && intersecciones[0] != null && intersecciones[1] != null) {
				ret = intersecciones[Constantes.centroArcoSup.indiceMasCercano(intersecciones)];
			}
		} else if (ret.getX() < bandaIzq) {
			Posicion intersecciones[] = Geometry.lineCircleIntersection(cornerInfIzq, cornerSupIzq, jugador, distancia);
			if (intersecciones != null && intersecciones[0] != null && intersecciones[1] != null) {
				ret = intersecciones[Constantes.centroArcoSup.indiceMasCercano(intersecciones)];
			}
		}

		if (ret.getY() > fondoSup) {
			Posicion intersecciones[] = Geometry.lineCircleIntersection(cornerSupIzq, cornerSupDer, jugador, distancia);
			if (intersecciones != null && intersecciones[0] != null && intersecciones[1] != null) {
				ret = intersecciones[Constantes.centroArcoSup.indiceMasCercano(intersecciones)];
			}
		} else if (ret.getY() < fondoInf) {
			Posicion intersecciones[] = Geometry.lineCircleIntersection(cornerInfIzq, cornerInfDer, jugador, distancia);
			if (intersecciones != null && intersecciones[0] != null && intersecciones[1] != null) {
				ret = intersecciones[Constantes.centroArcoSup.indiceMasCercano(intersecciones)];
			}
		}

		return ret;
	}

	private boolean abortarRegate(Posicion[] pos, Posicion[] posRivales, int indJugador) {
		Posicion rival = posRivales[pos[indJugador].indiceMasCercano(posRivales)];
		boolean hayPase = false;
		for (int i : pos[indJugador].indicesMasCercanos(pos, indJugador)) {
			if (pos[i].distancia(pos[indJugador]) < pos[i].distancia(rival)) {
				hayPase = forzarPase(pos, indJugador, i, true, posRivales);
				if (hayPase == true) {
					break;
				}
			}
		}
		return hayPase;
	}

	private double getRandomDeviation(double radio) {
		return (-radio + r.nextDouble() * 2 * radio);
	}

	private void addComando(Comando comando) {
		addComando(comando, true);
	}

	private void addComando(Comando comando, boolean abortarDesmarque) {
		if (abortarDesmarque) {
			Desmarque desmarque = null;
			for(ListIterator<Desmarque> it = desmarques.listIterator(); it.hasNext(); ) {
				desmarque = it.next();
				if (comando.getIndJugador() == desmarque.getIndiceJugador()) {
					desmarque.setEliminar(true);
				}
			}
		}
		if (comando instanceof ComandoGolpearBalon) {
			estoyAtacando = true;
		}

		comandos.add(comando);
	}

	private void actualizarDesmarques() {
		for (int i=0; i<desmarques.size(); ) {
			if (desmarques.get(i).getEliminar()) {
				desmarques.remove(i);
			} else if (desmarques.get(i).getTrasBalon() && !estoyAtacando) {
				desmarques.remove(i);
			} else {
				i++;
			}
		}
	}

	private boolean forzarPase(Posicion[] pos, int jugador1, int jugador2, boolean autoAdelantado, Posicion[] posRivales) {
		if (!darPase(pos, jugador1, jugador2, autoAdelantado, null)) {
			return patapumParriba(jugador1, pos, posRivales);
		}
		return true;
	}

	private boolean hayAlguienAlrededor(int indJugador, Posicion[] pos, Posicion[] posRivales, double distancia, boolean omitirPortero) {
		if (omitirPortero) {
			return pos[indJugador].distancia(posRivales[pos[indJugador].indiceMasCercano(posRivales, indPorteroRival)]) < distancia;
		} else {
			return pos[indJugador].distancia(posRivales[pos[indJugador].indiceMasCercano(posRivales)]) < distancia;
		}
	}

	private boolean hayViaLibre(int indJugador, Posicion[] pos, Posicion[] posRivales) {
		if (!hayRivalEnLinea(pos[indJugador], pos[indJugador].moverAngulo(pos[indJugador].angulo(calculaObjetivoJugador(indJugador, pos)), Constantes.ITERACIONES_GOLPEAR_BALON * Constantes.VELOCIDAD_MAX * 3 + Constantes.DISTANCIA_CONTROL_BALON), posRivales, true)) {
			return true;
		}
		return false;
	}

	private boolean darPasePorArriba(Posicion[] pos, int jugador1, int jugador2, Posicion destinoBola, Posicion[] posRivales) {

		double remate = detalle.getJugadores()[jugador1].getRemate();
		double distancia = pos[jugador1].distancia(destinoBola);

		double distanciaRelativa = 3*distancia/8 + 5*distancia/(8*remate);

		double fuerza, anguloVertical;

		if (distanciaRelativa < 15) {
			// Error?
			return false;
		} else if (distanciaRelativa < 20) {
			fuerza = 0.75 + 0.1 * (distanciaRelativa-15) / (20-15);
			anguloVertical = Constantes.ANGULO_VERTICAL;
		} else if (distanciaRelativa < 30) {
			fuerza = 0.85 + 0.1 * (distanciaRelativa-20) / (30-20);
			anguloVertical = Constantes.ANGULO_VERTICAL;
		} else {
			// Demasiado lejos
			return false;
		}
		desmarques.add(new Desmarque(jugador2));
		addComando(new ComandoGolpearBalon(jugador1, destinoBola.setDentroCampoJuego(), fuerza, anguloVertical));
		return true;
	}

	private boolean darPase(Posicion[] pos, int jugador1, int jugador2, boolean autoAdelantado, Posicion[] posRivales) {
		if (jugador1 == jugador2 || jugador2 == indPortero) {
			return false;
		}

		Posicion posHueco = pos[jugador2].moverAngulo(pos[jugador2].angulo(calculaObjetivoJugador(jugador2, pos)), pos[jugador1].distancia(pos[jugador2])/3);

		boolean paseAdelantado = false;
		if (posRivales != null) {
			if (autoAdelantado &&
					posHueco.distancia(posRivales[posHueco.indiceMasCercano(posRivales)]) > pos[jugador2].distancia(posRivales[posHueco.indiceMasCercano(posRivales)]) && 
					jugador2 != indPortero &&
					posHueco.getY() + Constantes.DISTANCIA_PENAL < Constantes.LARGO_CAMPO_JUEGO / 2) {
				paseAdelantado = true;
			}
		}

		Posicion destinoBola;
		if (!paseAdelantado) {
			destinoBola = pos[jugador2];
		} else {
			// Si el pase es adelantado, el destino de la bola es (distancia / 2) metros mas adelantada
			destinoBola = posHueco;
		}

		double remate = detalle.getJugadores()[jugador1].getRemate();
		double distancia = pos[jugador1].distancia(destinoBola);

		double distanciaRelativa = 3*distancia/8 + 5*distancia/(8*remate);

		double fuerza, anguloVertical;

		if (posRivales != null && hayRivalEnLinea(pos[jugador1], destinoBola, posRivales, false)) {
			if (paseAdelantado) {
				if (hayViaLibre(jugador1, pos, posRivales)) {
					return darPasePorArriba(pos, jugador1, jugador2, destinoBola, posRivales);
				}
			} else {
				if (!hayAlguienAlrededor(jugador1, pos, posRivales, distanciaRelativa/4, true)) {
					return darPasePorArriba(pos, jugador1, jugador2, destinoBola, posRivales);
				}
			}
			return false;
		}

		if (distanciaRelativa < 1) {
			// Error?
			return false;
		} else if (distanciaRelativa < 10) {
			fuerza = 0.7 * distanciaRelativa / 10;
			anguloVertical = 0;
		} else if (distanciaRelativa < 20) {
			fuerza = 0.7 + 0.1 * (distanciaRelativa-10) / (20-10);
			anguloVertical = 15;
		} else if (distanciaRelativa < 30) {
			fuerza = 0.8 + 0.1 * (distanciaRelativa-20) / (30-20);
			anguloVertical = 15;
		} else if (distanciaRelativa < 40) {
			fuerza = 0.9 + 0.1 * (distanciaRelativa-30) / (40-30);
			anguloVertical = 15;
		} else {
			// Demasiado lejos
			return false;
		}
		desmarques.add(new Desmarque(jugador2));
		addComando(new ComandoGolpearBalon(jugador1, destinoBola.setDentroCampoJuego(), fuerza, anguloVertical));
		return true;
	}

	private boolean hayRivalEnLinea(Posicion a, Posicion b, Posicion[] posRivales, boolean ignorarPortero) {
		try {
			for (Posicion rival:posRivales) {
				double distancia = 1.5 + 10*a.distancia(rival)/40;
				if (Geometry.distanceToSegment(rival, a, b) < distancia
						&& rival.distancia(a) < a.distancia(b)
						&& rival.distancia(b) < a.distancia(b)
				) {
					if (!ignorarPortero || !posRivales[indPorteroRival].equals(rival)) {
						return true;
					}
				}
			}
		} catch(IllegalArgumentException e) {

		}
		return false;
	}



	private class Desmarque {
		private int indiceJugador;
		private Posicion destino;
		private boolean trasBalon;
		private boolean eliminar = false;

		public Desmarque(int indiceJugador) {
			this.indiceJugador = indiceJugador;
			this.trasBalon = true;
		}

		public Desmarque(int indiceJugador, Posicion destino) {
			this.indiceJugador = indiceJugador;
			this.destino = destino;
		}

		public int getIndiceJugador() {
			return this.indiceJugador;
		}

		public Posicion getDestino() {
			return this.destino;
		}

		public ComandoIrA getComandoIrA(Posicion posJugador, Posicion balon, double alturaBalon) {
			if (destino == null) {
				return new ComandoIrA(indiceJugador, interceptaBalon(posJugador, balon, alturaBalon));
			} else {
				return new ComandoIrA(indiceJugador, destino.setDentroCampoJuego());
			}
		}

		public boolean haLlegado(Posicion posJugador, Posicion balon) {
			if (destino != null) {
				if (Math.abs(posJugador.getX() - this.destino.getX()) < 1
						&& Math.abs(posJugador.getY() - this.destino.getY()) < 1) {
					return true;
				}
			} else {
				if (Math.abs(posJugador.getX() - balon.getX()) < 1
						&& Math.abs(posJugador.getY() - balon.getY()) < 1) {
					return true;
				}
			}
			return false;
		}

		public void setEliminar(boolean eliminar) {
			this.eliminar = eliminar;
		}

		public boolean getEliminar() {
			return this.eliminar;
		}

		public void setTrasBalon(boolean trasBalon) {
			this.trasBalon = trasBalon;
		}

		public boolean getTrasBalon() {
			return this.trasBalon;
		}

		public boolean equals(Object o) {
			if (o == null) {
				return false;
			}
			if (!(o instanceof Desmarque)) {
				return false;
			}
			Desmarque d = (Desmarque) o;
			return (d.getIndiceJugador() == this.indiceJugador);
		}
	}

	private static class PosicionRelativa {

		static final float ANGLE_MIN_UNIT = 22.5f;

		static int anguloToPosicionRelativa(double angulo) {
			int pr = 0;
			if (angulo < 0) {
				angulo += 360;
			} else if (angulo >= 360) {
				angulo -= 360;
			}

			pr = (int) Math.floor(angulo / ANGLE_MIN_UNIT) % 16;

			return pr;
		}

		static double posicionRelativaToAngulo(int pr) {
			double angulo = 0;

			angulo = (pr+0.5) * ANGLE_MIN_UNIT;

			return angulo;
		}

		static int[] posicionRelativaContiguas(int pr) {
			int[] array = new int[7];

			array[0] = pr;
			array[1] = anguloToPosicionRelativa(posicionRelativaToAngulo(pr) - ANGLE_MIN_UNIT);
			array[2] = anguloToPosicionRelativa(posicionRelativaToAngulo(pr) + ANGLE_MIN_UNIT);
			array[3] = anguloToPosicionRelativa(posicionRelativaToAngulo(pr) - 2*ANGLE_MIN_UNIT);
			array[4] = anguloToPosicionRelativa(posicionRelativaToAngulo(pr) + 2*ANGLE_MIN_UNIT);
			array[5] = anguloToPosicionRelativa(posicionRelativaToAngulo(pr) - 3*ANGLE_MIN_UNIT);
			array[6] = anguloToPosicionRelativa(posicionRelativaToAngulo(pr) + 3*ANGLE_MIN_UNIT);

			return array;
		}

		static int posicionRelativaMasCercana(double anguloObjetivo, List<Integer> prList) {
			double anguloRes = 360;
			int prRes = 0;
			for (int pr : prList) {
				double angulo = posicionRelativaToAngulo(pr) - anguloObjetivo;
				if (angulo > 180) {
					angulo -= 360;
				}
				if (Math.abs(angulo) < Math.abs(anguloRes)) {
					anguloRes = angulo;
					prRes = pr;
				}
			}
			return prRes;
		}

	}

	private class SituacionJugador {
		private List<Integer> posicionRivales;
		private Posicion posJugador;
		private Posicion posObjetivo;
		private boolean tieneViaLibre;

		SituacionJugador(Posicion posJugador, Posicion posObjetivo) {
			this.posJugador = posJugador;
			this.posObjetivo = posObjetivo;
			this.tieneViaLibre = true;
			this.posicionRivales = new ArrayList<Integer>();
		}

		void addRival(Posicion posRival) {
			double angulo = Math.toDegrees(posJugador.angulo(posRival));
			posicionRivales.add(PosicionRelativa.anguloToPosicionRelativa(angulo));
			if (posJugador.distancia(posObjetivo) > posRival.distancia(posObjetivo)) {
				tieneViaLibre = false;
			}
		}

		private void checkLimits(List<Integer> salidas) {
			for (int i=0; i<16; i++) {
				Posicion p = posJugador.moverAngulo(PosicionRelativa.posicionRelativaToAngulo(i), Constantes.VELOCIDAD_MAX * Constantes.ITERACIONES_GOLPEAR_BALON * 1.35);
				if (!estaDentroCampo(p)) {
					salidas.remove(new Integer(i));
				}
			}
		}

		Posicion getSalida() {
			Posicion p = null;
			List<Integer> salidas = new ArrayList<Integer>();

			// Cargamos la lista con todas las salidas posibles
			for (int pr=0; pr<16; pr++) {
				salidas.add(pr);
			}

			// Recorremos las posiciones de los rivales
			for (int pr : posicionRivales) {
				// Eliminamos las mas cercanas
				for (int pr2 : PosicionRelativa.posicionRelativaContiguas(pr)) {
					salidas.remove(new Integer(pr2));
				}
			}

			// Evitamos que el jugador se salga del campo
			checkLimits(salidas);

			if (!salidas.isEmpty()) {
				if (tieneViaLibre) {
					p = posJugador.moverAngulo(posJugador.angulo(posObjetivo), Constantes.VELOCIDAD_MAX * Constantes.ITERACIONES_GOLPEAR_BALON * 1.5 + Constantes.DISTANCIA_CONTROL_BALON);
				} else {
					int pr = PosicionRelativa.posicionRelativaMasCercana(Math.toDegrees(posJugador.angulo(posObjetivo)), salidas);
					p = posJugador.moverAngulo(Math.toRadians(PosicionRelativa.posicionRelativaToAngulo(pr)), Constantes.VELOCIDAD_MAX * Constantes.ITERACIONES_GOLPEAR_BALON * 1.5 + Constantes.DISTANCIA_CONTROL_BALON);
				}
			}

			return meterDesplazamientoDentroCampo(posJugador, p);
		}
	}

	static class Geometry {
		/**
		 * Wrapper function to accept the same arguments as the other examples
		 * 
		 * @param x3
		 * @param y3
		 * @param x1
		 * @param y1
		 * @param x2
		 * @param y2
		 * @return
		 */
		public static double distanceToSegment(Posicion punto, Posicion linea1, Posicion linea2) throws IllegalArgumentException {
			final Point2D p3 = new Point2D.Double(punto.getX(), punto.getY());
			final Point2D p1 = new Point2D.Double(linea1.getX(), linea1.getY());
			final Point2D p2 = new Point2D.Double(linea2.getX(), linea2.getY());
			return distanceToSegment(p1, p2, p3);
		}

		/**
		 * Returns the distance of p3 to the segment defined by p1,p2;
		 * 
		 * @param p1
		 *                First point of the segment
		 * @param p2
		 *                Second point of the segment
		 * @param p3
		 *                Point to which we want to know the distance of the segment
		 *                defined by p1,p2
		 * @return The distance of p3 to the segment defined by p1,p2
		 */
		private static double distanceToSegment(Point2D p1, Point2D p2, Point2D p3) throws IllegalArgumentException {

			final double xDelta = p2.getX() - p1.getX();
			final double yDelta = p2.getY() - p1.getY();

			if ((xDelta == 0) && (yDelta == 0)) {
				throw new IllegalArgumentException("p1 and p2 cannot be the same point");
			}

			final double u = ((p3.getX() - p1.getX()) * xDelta + (p3.getY() - p1.getY()) * yDelta) / (xDelta * xDelta + yDelta * yDelta);

			final Point2D closestPoint;
			if (u < 0) {
				closestPoint = p1;
			} else if (u > 1) {
				closestPoint = p2;
			} else {
				closestPoint = new Point2D.Double(p1.getX() + u * xDelta, p1.getY() + u * yDelta);
			}

			return closestPoint.distance(p3);
		}

		public static Posicion lineIntersection(Posicion p1, Posicion p2, Posicion q1, Posicion q2)	{
			Posicion cp = null;
			double a1,b1,c1,a2,b2,c2,denom;
			a1 = p2.getY()-p1.getY();
			b1 = p1.getX()-p2.getX();
			c1 = p2.getX()*p1.getY()-p1.getX()*p2.getY();
			// a1x + b1y + c1 = 0 line1 eq
			a2 = q2.getY()-q1.getY();
			b2 = q1.getX()-q2.getX();
			c2 = q2.getX()*q1.getY()-q1.getX()*q2.getY();
			// a2x + b2y + c2 = 0 line2 eq
			denom = a1*b2 - a2*b1;
			if(denom != 0)
				cp = new Posicion((b1*c2 - b2*c1)/denom,(a2*c1 -
						a1*c2)/denom);
			else
			{
				// lines are parallel
			}
			return cp;
		}

		public static Posicion[] linePerpendicular(Posicion p1, Posicion p2) {
			Posicion[] p = new Posicion[2];
			double angulo = p1.angulo(p2);

			p[0] = new Posicion(p2.moverAngulo(angulo + Math.toRadians(90), 1));
			p[1] = new Posicion(p2.moverAngulo(angulo + Math.toRadians(-90), 1));

			return p;
		}

		/**
		 * Calculate the intersection of a ray and a sphere
		 * The line segment is defined from p1 to p2
		 * The sphere is of radius r and centered at sc
		 * There are potentially two points of intersection given by
		 *    p = p1 + mu1 (p2 - p1)
		 *    p = p1 + mu2 (p2 - p1)
		 * Return {null, null} if the ray doesn't intersect the sphere.
		 * 
		 * @param p1
		 * @param p2
		 * @param sc
		 * @param r
		 * @return
		 */
		public static Posicion[] lineCircleIntersection(Posicion p1, Posicion p2, Posicion sc, double r) {
			Posicion intersect1, intersect2;
			double a,b,c;
			double bb4ac;

			Posicion dp = new Posicion(p2.getX() - p1.getX(), p2.getY() - p1.getY());

			a = dp.getX() * dp.getX() + dp.getY() * dp.getY();
			b = 2 * (dp.getX() * (p1.getX() - sc.getX()) + dp.getY() * (p1.getY() - sc.getY()));
			c = sc.getX() * sc.getX() + sc.getY() * sc.getY();
			c += p1.getX() * p1.getX() + p1.getY() * p1.getY();
			c -= 2 * (sc.getX() * p1.getX() + sc.getY() * p1.getY());
			c -= r * r;
			bb4ac = b * b - 4 * a * c;
			if (a == 0 || bb4ac < 0) {
				intersect1 = null;
				intersect2 = null;
			} else {
				double mu1 = (-b + Math.sqrt(bb4ac)) / (2 * a);
				double mu2 = (-b - Math.sqrt(bb4ac)) / (2 * a);
				intersect1 = new Posicion(p1.getX() + mu1*(p2.getX()-p1.getX()), p1.getY() + mu1*(p2.getY()-p1.getY()));
				intersect2 = new Posicion(p1.getX() + mu2*(p2.getX()-p1.getX()), p1.getY() + mu2*(p2.getY()-p1.getY()));
			}

			return new Posicion[]{intersect1, intersect2};
		}

	}
}